"use strict";

// 字符串连接
function strJoin() {
    console.log("\n strJoin");

    let part1 = "七律·长征";
    let part2 = "毛泽东";
    let part3 = "1935年10月";
    let part4 = "红军不怕远征难，万水千山只等闲。";
    let part5 = "五岭逶迤腾细浪，乌蒙磅礴走泥丸。";
    let part6 = "金沙水拍云崖暖，大渡桥横铁索寒。";
    let part7 = "更喜岷山千里雪，三军过后尽开颜。";
    let all = part1 + "\n" + part2 + "\n" + part3 + "\n" + part4 + "\n" + part5 + "\n" + part6 + "\n" + part7;
    console.log("\nlenght=" + all.length);
    console.log("\n使用+连接字符串");
    console.log(all);

    console.log("\n反引号(键盘q键左上方那个) 和 ${} 组装字符串");
    console.log(`${part1}\n${part2}\n${part3}\n${part4}\n${part5}\n${part6}\n${part7}\n`)

    let all2 = "\
七律·长征 \
毛泽东 \
1935年10月 \
红军不怕远征难，万水千山只等闲。 \
五岭逶迤腾细浪，乌蒙磅礴走泥丸。 \
金沙水拍云崖暖，大渡桥横铁索寒。 \
更喜岷山千里雪，三军过后尽开颜。";
    console.log("\n双引号+左斜杠 写多行字符串，这样没有换行");
    console.log(all2);

    let all21 = "\
七律·长征 \n\
毛泽东 \n\
1935年10月 \n\
红军不怕远征难，万水千山只等闲。 \n\
五岭逶迤腾细浪，乌蒙磅礴走泥丸。 \n\
金沙水拍云崖暖，大渡桥横铁索寒。 \n\
更喜岷山千里雪，三军过后尽开颜。";
    console.log("\n双引号+回车+左斜杠 写多行字符串，这样如预期有换行");
    console.log(all21);

    let all3 = `
七律·长征
毛泽东
1935年10月
红军不怕远征难，万水千山只等闲。
五岭逶迤腾细浪，乌蒙磅礴走泥丸。
金沙水拍云崖暖，大渡桥横铁索寒。
更喜岷山千里雪，三军过后尽开颜。`;
    console.log("\n反引号(键盘q键左上方那个)直接写多行字符串");
    console.log(all3);
}

// 使用正则替换左起的空白字符
function leftTrim(str) {
    return str.replace(/^\s+/gm, '');
}

// 使用正则替换右起的空白字符
function rightTrim(str) {
    return str.replace(/\s+$/gm, '');
}

// 字符串trim
function strTrim() {
    console.log("\n strTrim");

    let ostr = " \t 2sapce+1tab 山外青山楼外楼 3space   ";
    // 原生的两端trim
    let trimstr = ostr.trim();
    let regTrim = leftTrim(rightTrim(ostr));
    let leftTrimstr = leftTrim(ostr);
    let rightTrimstr = rightTrim(ostr);

    console.log("ostr        :|" + ostr + "|");
    console.log("leftTrimstr :|" + leftTrimstr + "|\t <-- 使用正则替换左起的空白字符");
    console.log("rightTrimstr:|" + rightTrimstr + "|\t <-- 使用正则替换右起的空白字符");
    console.log("regTrim     :|" + regTrim + "|\t\t <-- 使用正则替换左起和右起的空白字符");
    console.log("trimstr     :|" + trimstr + "|\t\t <-- 使用String类 原生的 trim api");

}

function strArr() {
    console.log("\n strArr");

    let monthArr = [
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    ];

    // 数组 --'分隔符'--> 字符串
    let arr2String = monthArr.join('|');

    console.log("arr2String: " + arr2String);

    // 字符串 --'分隔符'--> 数组
    let weekdayStr = "Mon-Tue-Wed-Thu-Fri-Sat-Sun";

    let arrFromStrSplit = weekdayStr.split('-')

    console.log("arrFromStrSplit:");
    arrFromStrSplit.forEach((element, index) => {
        console.log("for each : " + index + ", " + element);
    })

}

function subStringSearchReplace() {
    console.log("\n subStringSearch");

    let ostr = "浔阳江头夜送客，枫叶荻花秋瑟瑟。主人下马客在船，举酒欲饮无管弦。醉不成欢惨将别，别时茫茫江浸月。忽闻水上琵琶声，主人忘归客不发。寻声暗问弹者谁，琵琶声停欲语迟。移船相近邀相见，添酒回灯重开宴。千呼万唤始出来，犹抱琵琶半遮面。转轴拨弦三两声，未成曲调先有情。弦弦掩抑声声思，似诉平生不得志。低眉信手续续弹，说尽心中无限事。轻拢慢捻抹复挑，初为《霓裳》后《六幺》。大弦嘈嘈如急雨，小弦切切如私语。嘈嘈切切错杂弹，大珠小珠落玉盘。间关莺语花底滑，幽咽泉流冰下难。冰泉冷涩弦凝绝，凝绝不通声暂歇。别有幽愁暗恨生，此时无声胜有声。银瓶乍破水浆迸，铁骑突出刀枪鸣。曲终收拨当心画，四弦一声如裂帛。东船西舫悄无言，唯见江心秋月白。沉吟放拨插弦中，整顿衣裳起敛容。自言本是京城女，家在虾蟆陵下住。十三学得琵琶成，名属教坊第一部。曲罢曾教善才服，妆成每被秋娘妒。五陵年少争缠头，一曲红绡不知数。钿头银篦击节碎，血色罗裙翻酒污。今年欢笑复明年，秋月春风等闲度。弟走从军阿姨死，暮去朝来颜色故。门前冷落鞍马稀，老大嫁作商人妇。商人重利轻别离，前月浮梁买茶去。去来江口守空船，绕船月明江水寒。夜深忽梦少年事，梦啼妆泪红阑干。我闻琵琶已叹息，又闻此语重唧唧。同是天涯沦落人，相逢何必曾相识！我从去年辞帝京，谪居卧病浔阳城。浔阳地僻无音乐，终岁不闻丝竹声。住近湓江地低湿，黄芦苦竹绕宅生。其间旦暮闻何物？杜鹃啼血猿哀鸣。春江花朝秋月夜，往往取酒还独倾。岂无山歌与村笛？呕哑嘲哳难为听。今夜闻君琵琶语，如听仙乐耳暂明。莫辞更坐弹一曲，为君翻作《琵琶行》。感我此言良久立，却坐促弦弦转急。凄凄不似向前声，满座重闻皆掩泣。座中泣下谁最多？江州司马青衫湿。";

    // 从头到尾 查找第一处
    // indexOf() 方法只返回查找到的第一个子字符串的起始下标值，如果没有找到则返回 -1。
    let firstPos = ostr.indexOf("琵琶");
    if (firstPos >= 0) {
        // substring 返回的子串包括 开始 处的字符，但不包括 结束 处
        let fstr = ostr.substring(0, firstPos);
        console.log("从头到尾 查找第一处'琵琶', 从头截取 : " + fstr);
    }

    // 从末尾向开头 查找第一处
    let tailPos = ostr.lastIndexOf("琵琶");
    if (tailPos >= 0) {
        let tstr = ostr.substring(tailPos);
        console.log("从尾到头 查找第一处'琵琶', 截取到尾 : " + tstr);
    }

    let findCount = 0;
    let findPos = 0;
    while (true) {
        let index = ostr.indexOf("琵琶", findPos);
        console.log(`from ${findPos}, find index=${index}`);
        if (index < 0) {
            break;
        }
        findCount += 1;
        findPos = index + 2;
    }
    console.log("搜索所有的'琵琶', 一共找到 : " + findCount + " 处 ");

    // 替换第一处 琵琶 为 PiPa
    let rpFirstStr = ostr.replace("琵琶", "PiPa");
    console.log("第一处 琵琶 --> PiPa \n" + rpFirstStr);

    // 替换所有命中的 琵琶 为 PiPa
    let rpStr = ostr.replaceAll("琵琶", "PiPa");
    console.log("所有 琵琶 --> PiPa \n" + rpStr);
}

function regStringSearchReplace() {
    console.log("\n regStringSearchReplace");

    let ostr = "百科熊猫词条  https://baike.baidu.com/item/%E5%A4%A7%E7%86%8A%E7%8C%AB/34935?fromtitle=%E7%86%8A%E7%8C%AB&fromid=162918&fr=aladdin http://127.0.0.1:8080/dokuwiki/doku.php?id=cpp-str2arr panda@163.com 大熊猫（学名：Ailuropoda melanoleuca）：属于食肉目、熊科、大熊猫亚科和大熊猫属唯一的哺乳动物，头躯长1.2-1.8米，尾长10-12厘米。体重80-120千克，最重可达180千克，体色为黑白两色，它有着圆圆的脸颊，大大的黑眼圈，胖嘟嘟的身体，标志性的内八字的行走方式，也有解剖刀般锋利的爪子。是世界上最可爱的动物之一。  panda@gmail.com  com.panda@gmail (010)88886666 ，或 022-22334455，或 02912345678 511122198812068932 41112219881206893X 18660000062 17000000026 ";

    // https://blog.csdn.net/weixin_42216520/article/details/104446628
    // https://www.cnblogs.com/zjz666/p/11429894.html

    // 两种 正则表达式 对象创建方式
    // 1、通过new创建正则：var box = new RegExp("hello", "ig");
    // 2、正则常量赋值:   var box = /hello/ig;
    // 修饰符	描述
    // i	执行对大小写不敏感的匹配。
    // g	执行全局匹配（查找所有匹配而非在找到第一个匹配后停止）。
    // m	执行多行匹配。

    // 身份证
    let idPattern = /\b(\d{17}|\d{14})(\d|[xX])\b/g;

    // 电话号码：eg：021-68686868  0511-6868686；
    let phonePattern = new RegExp("\\b(\\d{3,4}-)\\d{7,8}\\b", 'g');

    // 手机
    let mobilePhonePattern = /\b1\d{10}\b/g;

    // 邮箱
    let emailPattern = /\b[a-zA-Z0-9]\w*@[a-zA-Z0-9]+\.[a-zA-Z]{2,4}\b/g;

    // url
    let urlPattern = /(https?|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g;

    // 正则表达式 对象 方法调用
    // RegExp.test(), 如果存在返回true； 如果不存在就返回false
    // RegExp.exec(), 如果存在返回一个数组，数组里面存放着匹配的内容, 如果不存在，返回null
    // 试验发现 test exec 调用的顺序会导致不同的结果，貌似RegExp内部维护了一个搜索position，test调用和exec调用，都会移动这个搜索postion
    let execRes = null;
    while ((execRes = idPattern.exec(ostr)) !== null) { console.dir(execRes); } // 如果先call了 idPattern.test(ostr) , 只会循环一次。先call idPattern.exec(ostr) 能按期望找出两个匹配结果
    console.log('身份证 : ' + '  -- test --> ' + idPattern.test(ostr));

    console.log('电话号码 : ' + '  -- test --> ' + phonePattern.test(ostr));
    while ((execRes = phonePattern.exec(ostr)) !== null) { console.dir(execRes); }

    console.log('手机 : ' + '  -- test --> ' + mobilePhonePattern.test(ostr));
    while ((execRes = mobilePhonePattern.exec(ostr)) !== null) { console.dir(execRes); }

    console.log('邮箱 : ' + '  -- test --> ' + emailPattern.test(ostr));
    while ((execRes = emailPattern.exec(ostr)) !== null) { console.dir(execRes); }

    console.log('url : ' + '  -- test --> ' + urlPattern.test(ostr));
    while ((execRes = urlPattern.exec(ostr)) !== null) { console.dir(execRes); }

    // String 正则方法调用
    // String.match() 将字符串中匹配的内容捕获出来, 若加上量词g则一次性捕获所有匹配的内容放到数组里返回
    // String.search() 返回值是相匹配的子串的起始位置。 方法不执行全局匹配，它将忽略标志 g。
    console.log('身份证 : ' + idPattern + '  -- first index --> ' + ostr.search(idPattern));
    console.dir(ostr.match(idPattern));

    console.log('电话号码 : ' + phonePattern + '  -- first index --> ' + ostr.search(phonePattern));
    console.dir(ostr.match(phonePattern));

    console.log('手机 : ' + mobilePhonePattern + '  -- first index --> ' + ostr.search(mobilePhonePattern));
    console.dir(ostr.match(mobilePhonePattern));

    console.log('邮箱 : ' + emailPattern + '  -- first index --> ' + ostr.search(emailPattern));
    console.dir(ostr.match(emailPattern));

    console.log('url : ' + urlPattern + '  -- first index --> ' + ostr.search(urlPattern));
    console.dir(ostr.match(urlPattern));


    console.log('idPattern      replace    -> ' + ostr.replace(idPattern, '____ID____')); // 因为 idPattern 有g 修饰符，所以会替换所有
    console.log('idPattern      replaceAll -> ' + ostr.replaceAll(idPattern, '____ID____')); // 和 replace 效果一样 
    // 构建一个不带 g 的身份证正则 
    let idPatternFirst = /\b(\d{17}|\d{14})(\d|[xX])\b/;
    console.log('idPatternFirst replace    -> ' + ostr.replace(idPatternFirst, '____ID____')); // 因为没有有g 修饰符，所以只替换第一个
    //console.log('idPatternFirst replaceAll -> ' + ostr.replaceAll(idPatternFirst, '____ID____')); //TypeError: String.prototype.replaceAll called with a non-global RegExp argument
}

function sprintfConvert(match, nosign) {
    if (nosign) {
        match.sign = '';
    } else {
        match.sign = match.negative ? '-' : match.sign;
    }
    let l = match.min - match.argument.length + 1 - match.sign.length;
    let pad = new Array(l < 0 ? 0 : l).join(match.pad);
    if (!match.left) {
        if (match.pad == "0" || nosign) {
            return match.sign + pad + match.argument;
        } else {
            return pad + match.sign + match.argument;
        }
    } else {
        if (match.pad == "0" || nosign) {
            return match.sign + match.argument + pad.replace(/0/g, ' ');
        } else {
            return match.sign + match.argument + pad;
        }
    }
}


function sprintf() {
    //console.log("\n sprintf");

    // 入参判断
    //console.dir(arguments);
    if (typeof arguments == "undefined") {
        return null;
    }
    if (arguments.length < 1) {
        return null;
    }
    if (typeof arguments[0] != "string") {
        return null;
    }

    /*
        https://www.php.cn/js-tutorial-9219.html
    
        1.%% - 返回百分号本身
        2.%b - 二进制数字
        3.%c - ASCII对应的字符
        4.%d - 整数
        5.%f - 浮点数
        6.%o - 八进制数字
        7.%s - 字符串
        8.%x - 16进制数字 (小写字母形式)
        9.%X - 16进制数字 (大写字母形式)
        
        在 % 号和通配字符之间可用的选项包括 (比如 %.2f)：
        
        1.+ (强制在数字前面显示 + 和 - 符号作为正负数标记。缺省情况下只有负数才显示 - 符号)
        2.- (变量左对齐)
        3.0 (使用0作为右对齐的填充字符)
        4.[0-9] (设置变量的最小宽度)
        5..[0-9] (设置浮点数精度或字符串的长度)
    //*/

    let exp = /(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g;

    let formatStr = arguments[0];
    let loop = 0;
    let match = null;

    let matcheArr = new Array();
    let nonConversStrArr = new Array();
    let convCount = 0;
    let stringPosStart = 0;
    let stringPosEnd = 0;
    let matchPosEnd = 0;

    //console.log("exp.lastIndex : " + exp.lastIndex); //, 下一次正则匹配(formatStr)的起始点。 The index at which to start the next match
    while (match = exp.exec(formatStr)) {

        /*
        https://cloud.tencent.com/developer/section/1192049
        sprintf 函数用到的表达式对象 exp.exec 返回值的结构如下：
        
                    /(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g
        match[0] =  整个正则表达式对象 某一次 匹配 对应的match结果           
        match[1] =   (%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))      对应的match结果
        match[2] =     ([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX]))       对应的match结果,如果没有设置为undefined
        match[3] =          (\-)?                                                  对应的match结果,如果没有设置为undefined
        match[4] =               (\+|\x20)?                                        对应的match结果,如果没有设置为undefined
        match[5] =                         (0)?                                    对应的match结果,如果没有设置为undefined
        match[6] =                             (\d+)?                              对应的match结果,如果没有设置为undefined
        match[7] =                                   (\.(\d)?)?                    对应的match结果,如果没有设置为undefined
        match[8] =                                      (\d)?                      对应的match结果,如果没有设置为undefined
        match[9] =                                             ([bcdfosxX])        对应的match结果,如果没有设置为undefined
        
        index ： 整个正则表达式对象 某一次 匹配到的字符串 的 起始下标。The 0-based index of the match in the string.
        */
        //console.log("\n exp.exec loop " + loop);
        ++loop;
        //console.dir(match);

        // 下一次正则匹配(formatStr)的起始点。 The index at which to start the next match
        //console.log("exp.lastIndex : " + exp.lastIndex + ', formatStr 待匹配：' + formatStr.substring(exp.lastIndex));

        if (match[9]) {
            // match[9] 对应正则中的这部分  ([bcdfosxX])
            // match[9]非空表示 要做 参数转换拼装
            convCount += 1;
        }

        stringPosStart = matchPosEnd;
        matchPosEnd = exp.lastIndex; // lastIndex 是 下一次正则匹配(formatStr)的起始点
        stringPosEnd = exp.lastIndex - match[0].length;

        // 计算 这次匹配结果中，不参与 变换组装 的部分，放置到 nonConversStrArr 备用
        nonConversStrArr[nonConversStrArr.length] = formatStr.substring(stringPosStart, stringPosEnd);
        //console.log("这次匹配结果中，不参与 变换组装 的部分，放置到 nonConversStrArr 备用 -> |" + nonConversStrArr[nonConversStrArr.length - 1] + '|');

        // 正则匹配的有效信息,记录到 matcheArr 数组中
        matcheArr[matcheArr.length] = {
            match: match[0],
            left: match[3] ? true : false, // match[3] 对应正则表达式中的 (\-)?           , 这是 是否左对齐 的标识
            sign: match[4] || '',          // match[4] 对应正则表达式中的 (\+|\x20)?      , 这是 是否显示正负号 的标识
            pad: match[5] || ' ',          // match[5] 对应正则表达式中的 (0)?            , 这是 是否补0填充 的标识
            min: match[6] || 0,            // match[6] 对应正则表达式中的 (\d+)?          , 这是 最小填充长度 的标识
            precision: match[8],           // match[8] 对应正则表达式中的 (\d)?           , 这是 浮点显示精度 的标识
            code: match[9] || '%',         // match[9] 对应正则表达式中的 ([bcdfosxX])    , 这是 参数转换拼装 的标识
            negative: parseInt(arguments[convCount]) < 0 ? true : false,  // 判断 格式化用到的 入参 的 正负 情况
            argument: String(arguments[convCount])                        // 格式化用到的 入参
        };
    }

    nonConversStrArr[nonConversStrArr.length] = formatStr.substring(matchPosEnd);
    //console.log("匹配完成, 剩余的部分 nonConversStrArr 备用 -> |" + nonConversStrArr[nonConversStrArr.length - 1] + '|');

    if (matcheArr.length == 0) {
        // 没有需要 替换拼装的
        return formatStr; 
    }
    if ((arguments.length - 1) < convCount) {
        console.log("入参不足，需要 " + convCount + ', 只有' + (arguments.length - 1));
        return null;
    }

    let rtnString = '';
    var i = null;
    for (i = 0; i < matcheArr.length; ++i) {
        let substitution = '';

        switch (matcheArr[i].code) {
            case '%':
                substitution = '%'
                break;
            case 'f':
                let prec = matcheArr[i].precision ? matcheArr[i].precision : 6; // 浮点精度
                matcheArr[i].argument = String(Math.abs(parseFloat(matcheArr[i].argument)).toFixed(prec));
                substitution = sprintfConvert(matcheArr[i]);
                break;
            case 'd':
                matcheArr[i].argument = String(Math.abs(parseInt(matcheArr[i].argument)));
                substitution = sprintfConvert(matcheArr[i]);
                break;
            case 's':
                matcheArr[i].argument = matcheArr[i].argument.substring(0, matcheArr[i].precision ? matcheArr[i].precision : matcheArr[i].argument.length)
                substitution = sprintfConvert(matcheArr[i], true);
                break;
            case 'c':
                matcheArr[i].argument = String(Math.abs(parseInt(matcheArr[i].argument)));
                substitution = sprintfConvert(matcheArr[i]);
                break;
            case 'b':
                matcheArr[i].argument = String(Math.abs(parseInt(matcheArr[i].argument)).toString(2));
                substitution = sprintfConvert(matcheArr[i], true);
                break;
            case 'o':
                matcheArr[i].argument = String(Math.abs(parseInt(matcheArr[i].argument)).toString(8));
                substitution = sprintfConvert(matcheArr[i]);
                break;
            case 'x':
                matcheArr[i].argument = String(Math.abs(parseInt(matcheArr[i].argument)).toString(16));
                substitution = sprintfConvert(matcheArr[i]);
                break;
            case 'X':
                matcheArr[i].argument = String(Math.abs(parseInt(matcheArr[i].argument)).toString(16));
                substitution = sprintfConvert(matcheArr[i]).toUpperCase();
                break;
            default:
        }

        rtnString += nonConversStrArr[i];
        rtnString += substitution;

    }
    rtnString += nonConversStrArr[i];

    return rtnString;
}

function strFormat() {

    // 1.%% - 返回百分号本身
    // 2.%b - 二进制数字
    // 4.%d - 整数
    // 6.%o - 八进制数字
    // 8.%x - 16进制数字 (小写字母形式)
    // 9.%X - 16进制数字 (大写字母形式)
    let iv = 127;
    let fmtStr = sprintf("二进制:%b, 八进制:%o, 十进制:%d, 十六进制:%x %X, \n左对齐10位显示|%-10d|, \n右对齐10位显示|%10d|, \n右对齐10位显示0填充|%010d|",
        iv, iv, iv, iv, iv, iv, iv, iv);
    console.log("\n整数 -> " + fmtStr);

    // 5.%f - 浮点数
    let fv = 3.141592654;
    fmtStr = sprintf("浮点两位精度|%.2f|, \n浮点三位精度12位左对齐|%-12.3f|, \n浮点三位精度12位右对齐|%12.3f|, \n浮点五位精度12位右对齐0填充|%012.5f|",
        fv, fv, fv, fv, fv);
    console.log("\n浮点数 -> " + fmtStr);

    // 3.%c - ASCII对应的字符
    // 7.%s - 字符串
    let strarg = 'asdfghjkl';
    fmtStr = sprintf("%% 字符:%c 字符:%c, 字符串%s, 字符串截取前3位%.3s, \n20位左对齐|%-20s|, \n20位右对齐|%20s|, \n20位右对齐0填充|%020s|",
        65, strarg.charCodeAt(0), strarg, strarg, strarg, strarg, strarg);
    console.log("\n字符串 -> " + fmtStr);

    fmtStr = sprintf("没有需要替换拼装的", 65);
    console.log("\n没有需要替换拼装的 -> " + fmtStr);
}

function demoLogic() {
    // 字符串连接
    strJoin();

    // 字符串trim
    strTrim();

    // 字符串\数组互转
    strArr();

    // 字符串简单查询替换
    subStringSearchReplace()

    // 字符串正则查询替换
    regStringSearchReplace()

    // 字符串格式化
    strFormat();

}

export { demoLogic, leftTrim, rightTrim, sprintf };